import { svgNamespace, xlinkNamespace } from './dom'
import { DomToSvgOptions, walkNode } from './traversal'
import { createStackingLayers } from './stacking'
import { createIdGenerator } from './util'
import { isCSSFontFaceRule, unescapeStringValue } from './css'
import cssValueParser from 'postcss-value-parser'

export { DomToSvgOptions }

export function documentToSVG(document: Document, options?: DomToSvgOptions): XMLDocument {
	return elementToSVG(document.documentElement, options)
}

export function elementToSVG(element: Element, options?: DomToSvgOptions): XMLDocument {
	const svgDocument = element.ownerDocument.implementation.createDocument(svgNamespace, 'svg', null) as XMLDocument

	const svgElement = (svgDocument.documentElement as unknown) as SVGSVGElement
	svgElement.setAttribute('xmlns', svgNamespace)
	svgElement.setAttribute('xmlns:xlink', xlinkNamespace)
	svgElement.append(
		svgDocument.createComment(` Generated by dom-to-svg from ${element.ownerDocument.location.href} `)
	)

	// Copy @font-face rules
	const styleElement = svgDocument.createElementNS(svgNamespace, 'style')
	for (const styleSheet of element.ownerDocument.styleSheets) {
		let rules: CSSRuleList | undefined
		try {
			rules = styleSheet.rules
		} catch (error) {
			console.error('Could not access rules of styleSheet', styleSheet, error)
		}
		// Make font URLs absolute (need to be resolved relative to the stylesheet)
		for (const rule of rules ?? []) {
			if (!isCSSFontFaceRule(rule)) {
				continue
			}
			const styleSheetHref = rule.parentStyleSheet?.href
			if (styleSheetHref) {
				const parsedSourceValue = cssValueParser(rule.style.src)
				parsedSourceValue.walk(node => {
					if (node.type === 'function' && node.value === 'url' && node.nodes[0]) {
						const urlArgumentNode = node.nodes[0]
						if (urlArgumentNode.type === 'string' || urlArgumentNode.type === 'word') {
							urlArgumentNode.value = new URL(
								unescapeStringValue(urlArgumentNode.value),
								styleSheetHref
							).href
						}
					}
				})
				rule.style.src = cssValueParser.stringify(parsedSourceValue.nodes)
			}
			styleElement.append(rule.cssText, '\n')
		}
	}
	svgElement.append(styleElement)

	walkNode(element, {
		svgDocument,
		currentSvgParent: svgElement,
		stackingLayers: createStackingLayers(svgElement),
		parentStackingLayer: svgElement,
		getUniqueId: createIdGenerator(),
		labels: new Map<HTMLLabelElement, string>(),
		options: {
			captureArea: options?.captureArea ?? element.getBoundingClientRect(),
			keepLinks: options?.keepLinks !== false,
		},
	})

	const bounds = options?.captureArea ?? element.getBoundingClientRect()
	svgElement.setAttribute('width', bounds.width.toString())
	svgElement.setAttribute('height', bounds.height.toString())
	svgElement.setAttribute('viewBox', `${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}`)

	return svgDocument
}

export { inlineResources } from './inline'
